package cn.kgc.security;

import cn.hutool.core.util.StrUtil;
import cn.kgc.entity.Users;
import cn.kgc.service.MenusService;
import cn.kgc.service.UsersService;
import cn.kgc.util.JwtUtil;
import cn.kgc.util.RedisUtil;
import cn.kgc.util.ResponseUtil;
import cn.kgc.util.SystemConstant;
import cn.kgc.util.resultutil.Result;
import cn.kgc.util.resultutil.ResultConstant;
import cn.kgc.util.resultutil.exception.HttpException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

import static cn.kgc.util.SystemConstant.LOGIN_URL;
import static cn.kgc.util.SystemConstant.USER_STATE_RESIGN;

/**
 * @author YC
 * 验证令牌的过滤器，继承OncePerRequestFilter保证一个请求只执行一次
 */
public class TokenAuthenticationFilter extends OncePerRequestFilter {

    private final JwtUtil jwtUtil;
    private final RedisUtil redisUtil;
    private final UsersService usersService;
    private final MenusService menusService;

    public TokenAuthenticationFilter(JwtUtil jwtUtil, RedisUtil redisUtil, UsersService usersService, MenusService menusService) {
        this.jwtUtil = jwtUtil;
        this.redisUtil = redisUtil;
        this.usersService = usersService;
        this.menusService = menusService;
    }

    /**
     * 过滤请求进行令牌验证
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 判断请求地址是否为登录地址
        if (LOGIN_URL.equals(request.getRequestURI())) {
            // 登录地址直接放行
            filterChain.doFilter(request, response);
            return;
        }
        // 不是登录地址，验证令牌是否正确
        String token = request.getHeader("token");
        // 令牌为空则抛出异常
        if (StrUtil.isBlank(token)) {
            ResponseUtil.setResponse(response, Result.error(new HttpException(ResultConstant.LOGIN_ERROR)));
            return;
        }
        try {
            // 令牌不为空，验证令牌是否有效
            String userId = jwtUtil.getUserId(token, false);
            // 根据用户ID获取认证信息
            UsernamePasswordAuthenticationToken authentication = getAuthentication(userId);
            if (authentication == null) {
                ResponseUtil.setResponse(response, Result.error(new HttpException(ResultConstant.LOGIN_ERROR)));
                return;
            }
            // 令牌解析正常则保存用户登录信息（SecurityContextHolder可以在其他控制器中获得信息）
            SecurityContextHolder.getContext().setAuthentication(authentication);
            // 请求放行
            filterChain.doFilter(request, response);
        } catch (Exception e) {
            // 解析令牌抛出异常 封装
            ResponseUtil.setResponse(response, Result.error(new HttpException(ResultConstant.LOGIN_ERROR)));
        }

    }

    /**
     * 获取用户信息
     */
    private UsernamePasswordAuthenticationToken getAuthentication(String userId) {
        // 获取用户信息
        Users users = usersService.getById(userId);
        // 使用令牌时可以正常获得Id 但后台将用户删除或用户离职 可能产生user=null
        if (users == null || users.getState() == USER_STATE_RESIGN) {
            // 返回null，统一让过滤器抛异常
            return null;
        }
        // 获取用户权限信息
        Object temp = redisUtil.get(SystemConstant.REDIS_PRE_AUTHORITIES + users.getRoleId());
        List<SimpleGrantedAuthority> authorityList = null;
        if (temp != null) {
            // 缓存不为空从缓存获取数据
            authorityList = (List<SimpleGrantedAuthority>) temp;
        } else {
            // 缓存为空从数据库获取数据
            authorityList = menusService.getAuthoritiesByUserId(users.getRoleId());
            // 将数据存入缓存
            redisUtil.set(SystemConstant.REDIS_PRE_AUTHORITIES + users.getRoleId(), authorityList);
        }
        // 返回封装用户信息和权限信息
        return new UsernamePasswordAuthenticationToken(users, null, authorityList);
    }

}